package com.pansoft.hd.multisig;

import androidx.annotation.NonNull;

import com.google.common.collect.ImmutableList;
import com.pansoft.openplanet.blockchain.cypto.util.HexUtils;
import com.pansoft.openplanet.util.BalanceUtil;
import com.pansoft.openplanet.util.BtcNetParamUtil;
import com.pansoft.openplanet.util.TCMathUtil;
import com.utilcode.util.LogUtils;

import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.DumpedPrivateKey;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.core.Utils;
import org.bitcoinj.crypto.TransactionSignature;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.spongycastle.util.encoders.Hex;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static org.bitcoinj.core.Utils.HEX;

/**
 * 多签钱包生成，交易
 * @author wang
 */
public class MultiSigUtil {

    private static NetworkParameters networkParameters = MainNetParams.get();

    /**
     * 生成新的多签地址
     * @param redeemScript script
     */
    public static Address createMultiSigAddress(Script redeemScript) {
        //得到多签地址
        Address address = LegacyAddress.fromScriptHash(networkParameters, Utils.sha256hash160(redeemScript.getProgram()));
        System.out.println("多签地址：" + address);
        return address;
    }

    @NonNull
    /**
     * 生成redeem script
     * * @param threshold 最少签名数量(n-m中的n)
     * * @param pubKeys 所有共同拥有者的公钥，顺序不能乱
     */
    public static Script getRedeemScript(int threshold, List<String> pubKeys) {

        List<ECKey> spouseKeys = new ArrayList<>();
        for (String publicKey :
                pubKeys) {
            //由公钥得到EcKey
            ECKey ecKey = ECKey.fromPublicOnly(Utils.HEX.decode(publicKey));
            spouseKeys.add(ecKey);
        }
        List<ECKey> keys = ImmutableList.copyOf(spouseKeys);
        //打印所有公钥
        for (ECKey key :
                keys) {
            System.out.println(key.getPublicKeyAsHex());
        }
        //生成redeem script
        Script redeemScript = ScriptBuilder.createMultiSigOutputScript(threshold, keys);

        System.out.println("redeem script：" + HexUtils.toHex(redeemScript.getProgram()));
        return redeemScript;
    }

    /**
     * 创建交易流程
     * 请求utxo
     * createMultiSigTx--> getHexTx -->作为原始交易的hex值传到服务器
     * createMultiSigHashTx --> 得到原始交易的hash值，传给服务器
     * signTxHash --> 将原始交易的hash，签名，结果上传服务器
     */
    /**
     * 创建多签交易, 返回改多签交易
     * @param feePb 手续费 如 20 单位是sat/byte
     */
    public static Transaction createMultiSigTx(String value, String payAddress,
                                               String receiveAddress, List<String> txHexList,String feePb) {
        //构建交易
        Coin amount = Coin.valueOf(BalanceUtil.btcToBiInteger(value).longValue());
        Transaction contract = new Transaction(networkParameters);
        System.out.println(contract.getOutputs());

        //utxo
//        byte[] bytes = HEX.decode("0200000000010184c319cab78cc4a8c61a3af5eee7df26060bc7376382e7c8b61ecd5445ac25970100000017160014c0f022900a4130a4cc766f8c3d668dc7014c2854feffffff0282fc7a4b0100000017a9143f055b47b85942dea423303348da4dceb69825a48729fb63000000000017a914882c0596a48ee7a29649f79cc221c3491c0a867187024730440220645197a897b56313d0d960e8a94cdfcb0a989155a1d43a4ff06f6f446ae2119802200dcfba425315946776842d00000fe1619a67f85808ee6f0972e20422c5044671012102d3fb82e3f27adf68166187ab242ba0a1c55f358b3d003cfbd93dc286027d60e3f9851800");
//        Transaction transaction = networkParameters.getDefaultSerializer().makeTransaction(bytes);
//        TransactionOutput output = transaction.getOutput(1);
//        //utxo作为交易的输入
//        TransactionInput input = contract.addInput(output);
        Coin totalValue = parseTxHexOutput(payAddress, contract, txHexList);

        //对方收款地址
        Address toAddress = Address.fromString(networkParameters, receiveAddress);
        Script outputScript = ScriptBuilder.createOutputScript(toAddress);
        //对方的地址、金额作为一个输出
        contract.addOutput(amount, outputScript);

        //本次交易的找零地址，仍旧找回多签钱包地址
        Address changeAddress = LegacyAddress.fromBase58(networkParameters, payAddress);
        Script changeScript = ScriptBuilder.createOutputScript(changeAddress);
        //计算手续费，为了方便，输出为2，不考虑输出为1的情况
        int txSize = BtcNetParamUtil.computeBtcTxSize(contract.getInputs().size(), 2);
        long fee = (long) TCMathUtil.mul(txSize, Double.valueOf(feePb));
        LogUtils.d("所需手续费", fee);
        //utxo减去手续费是找零金额，和找零地址作为另一个输出
        Coin changeValue = totalValue.minus(amount).minus(Coin.valueOf(fee));
        //如果减去手续费的找零金额为0了,那么不找零,否则会造成dust异常
        if (!changeValue.equals(Coin.valueOf(0))) {
            contract.addOutput(changeValue, changeScript);
        }

        return contract;
    }

    /**
     * {
     *     "result": "success",
     *     "data": [
     *         {
     *             "transactionIndex": "",
     *             "confirmations": "739",
     *             "value": "6530361",
     *             "n": "1",
     *             "script": "a914882c0596a48ee7a29649f79cc221c3491c0a867187",
     *             "transactionHash": "a4ad3c64ca830f06bc7bc25432a74c271443817c40be84f8edbe9e8f23f67f7f"
     *         },
     *         {
     *             "transactionIndex": "",
     *             "confirmations": "736",
     *             "value": "10000",
     *             "n": "0",
     *             "script": "a914882c0596a48ee7a29649f79cc221c3491c0a867187",
     *             "transactionHash": "d8398ff0e4eeb054775f7e7f3dc82baf8bbd4aae94fa12544e230450a7439a71"
     *         }
     *     ]
     * }
     */
    public static Coin parseTxHexOutput(String payAddress, Transaction contract, List<String> txHexList) {
        Coin value = Coin.valueOf(0);
        for (String txHex :
                txHexList) {
            byte[] txBytes = HEX.decode(txHex);
            Transaction transaction = networkParameters.getDefaultSerializer().makeTransaction(txBytes);
            List<TransactionOutput> outputs = transaction.getOutputs();
            for (TransactionOutput txOut :
                    outputs) {

                if (txOut.getAddressFromP2SH(networkParameters) != null &&
                        txOut.getAddressFromP2SH(networkParameters).toBase58().equals(payAddress)) {
                    contract.addInput(txOut);
                    value = value.add(txOut.getValue());
                }
            }
        }
        return value;
    }

    /**
     * 得到原始交易的hex值，传到服务器用
     * @param contract 原始交易
     * @return hex
     */
    public static String getHexTx(Transaction contract) {
        //原始的交易，转成hex传给服务器
        String hexString = new String(Hex.encode(contract.bitcoinSerialize()));
        System.out.println(contract.getTxId().toString());
        System.out.println("原始交易:" + hexString);

        return hexString;
    }
    /**
     * 使用redeem script对原始交易hash处理
     * @param contract
     * @param redeemScript
     * @return
     */
    public static List<String> createMultiSigHashTx(int inputsNum, Transaction contract, Script redeemScript) {
        List<String> signHashList = new ArrayList<>();
        //待签名的交易签名hash
        for (int i = 0; i < inputsNum; i++) {
            Sha256Hash signHash = contract.hashForSignature(i, redeemScript, Transaction.SigHash.ALL, false);
            System.out.println("待发送的hash:" + signHash.toString());
            signHashList.add(signHash.toString());
        }
        return signHashList;
    }

    /**
     * 对交易hash做签名处理
     * @param signHashList 待签名交易
     * @param prvKey, base58格式
     * @return 签名转成byte数组
     */
    public static List<String> signTxHash(List<String> signHashList, String prvKey) {
        List<String> signatureBytesList = new ArrayList<>();
        for (String signHash :
                signHashList) {
            ECKey signKey = DumpedPrivateKey.fromBase58(networkParameters, prvKey).getKey();
            ECKey.ECDSASignature partySignature = signKey.sign(Sha256Hash.wrap(signHash));
            TransactionSignature partyTransactionSignature = new TransactionSignature(partySignature, Transaction.SigHash.ALL, false);
            System.out.println("待发送的我的hash签名" + Arrays.toString(partyTransactionSignature.encodeToBitcoin()));
            signatureBytesList.add(HEX.encode(partyTransactionSignature.encodeToBitcoin()));
        }

        return signatureBytesList;
    }


    /**
     * 确认交易，将hex的交易发送到服务器，推送到链上
     * @param hexTx 原始交易的hex值
     * @param redeemScript 多签钱包的script
     * @param signatureBytesList 各方的签名后txHash
     * @return 完整签名后的交易hex值
     */
    public static String completeMultiSigTx(String hexTx, Script redeemScript, List<List<String>> signatureBytesList) {
        byte[] txBytes = HEX.decode(hexTx);
        Transaction transaction = networkParameters.getDefaultSerializer().makeTransaction(txBytes);
//        TransactionOutput output = transaction.getOutput(1);
        //utxo作为交易的输入
        List<TransactionInput> inputs = transaction.getInputs();
        //        TransactionOutput output = new TransactionOutput(networkParameters, null, Coin.valueOf(22000),
//                Utils.HEX.decode("a9146d03527f398914c9c8728c757e2a49bf120869b387"));

//        //1Gfbaoj3GqqbYd2m7NS2MTSfrwfVPjdiE8
//        ECKey signKey3 = DumpedPrivateKey.fromBase58(networkParameters, "KxL2tTtE78rTMhPo1QiV3xEEfAiYnkTuTqyTwwvX9e5PFjS2HAmV").getKey();
        //2N4fRNkMW5eXiTjUiF7mJpPyzTHbUsLmf8V
//        ECKey signKey3 = DumpedPrivateKey.fromBase58(networkParameters, "cMvPjCzSXYhb9nau9dc8n4ut2zCpvnPkUzFYzyZoQXQSRvSjAbzc").getKey();
//
//        ECKey.ECDSASignature party3Signature = signKey3.sign(sighash);
//        TransactionSignature party3TransactionSignature = new TransactionSignature(party3Signature, Transaction.SigHash.ALL, false);
        List<Script> scriptList = new ArrayList<>();
        for (int i = 0; i < signatureBytesList.size(); i++) {
            List<TransactionSignature> signatures = new ArrayList<>();
            for (String bytes :
                    signatureBytesList.get(i)) {
                try {
                    TransactionSignature transactionSignature = TransactionSignature.decodeFromBitcoin(HEX.decode(bytes), true, true);
                    signatures.add(transactionSignature);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            // 创建P2SH多签的input script
            Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(signatures, redeemScript);
            System.out.println("inputScript-->" + inputScript);
            scriptList.add(inputScript);
        }



//        System.out.println(inputScript.getChunks().size() == 4);

        // Assert that the input script created contains the original multisig
        // script as the last chunk
//        ScriptChunk scriptChunk = inputScript.getChunks().get(inputScript.getChunks().size() - 1);
//        Assert.assertArrayEquals(scriptChunk.data, redeemScript.getProgram());

        // Create regular multisig input script
//        Script inputScript = ScriptBuilder.createMultiSigInputScript(ImmutableList.of(party3TransactionSignature, party2TransactionSignature));
//        System.out.println("inputScript-->" + inputScript);

//        transaction
        for (int i = 0; i < inputs.size(); i++) {
            inputs.get(i).setScriptSig(scriptList.get(i));
        }
//        input.verify(output);
        //对外广播的hex
        String hexString = new String(Hex.encode(transaction.bitcoinSerialize()));
        System.out.println(hexString);
        LogUtils.d("交易的真实大小", hexString.length()/2);
        return hexString;
    }


//    private static List<UTXO> getUtxoList() {
//        String txHash = "d8398ff0e4eeb054775f7e7f3dc82baf8bbd4aae94fa12544e230450a7439a71";
//        String txHash2 = "a4ad3c64ca830f06bc7bc25432a74c271443817c40be84f8edbe9e8f23f67f7f";
//
//        String txHex = "02000000000101d2de18e2e633c8cca6a882c33f59967d10b06a3435965d37c58ad80b91f11ca90100000017160014429dcdf5dd13fdb2ffb8426874600778f155df08feffffff02102700000000000017a914882c0596a48ee7a29649f79cc221c3491c0a867187a5623a000000000017a914599d7cf45392bf20c16a7a9af3c831f21c689ff28702473044022031ab3042ebe94275353ca796985cf07d087fa438e0f505fe605433ada8efe6e302205032055b0033700ae82108dcef9456a7f12be0dc7ae2c20123786b118a66c9f501210388d61412b593e4fbb156f73a7bd68002595b26c5768d3c3e0e36401de287a24606861800";
//        String txHex2 = "01000000011362aff895586b8e51885f2c657dd32d5cd39e608810a107f18e76f6faae12fe01000000fdfd00004830450221009a93eb401c7d21552ebd20edf482ddf328f6a6e366f2ce529782181c27a060b0022073fd2b6d4733a8b48b1518701b740d80ac4b89ef5f439d1c7767fd64dac5aaef0147304402205bc6dcd92a5c1bc1dcadaed573b5e45a733aa1f9eb0de931e79ec745b961f59702203ca45579dc219f20a6bf48f414311f5a93ac04a3085f240b1dce7f52d299cd7d014c69522102d8b3a0d0f5bd6146abe8d7a1b5121e9bdbd3f2ca53aa4e4d525c67944e6561e3210236cf178423fdce9ad8b975a597d26b164a71423687b1112f64f50abcbcbf387c2102e93dbc5af84c6ff4f39bc1084436c3e6d8c088fc7025827217b44cdb7b8da24a53aeffffffff02803e0000000000001976a914edc96705498831b16782d439fa93164bc5c8db6f88ac39a563000000000017a914882c0596a48ee7a29649f79cc221c3491c0a86718700000000";
//
//        byte[] txBytes = HEX.decode(txHex);
//        Transaction transaction = networkParameters.getDefaultSerializer().makeTransaction(txBytes);
//        TransactionOutput output = transaction.getOutput(1);
//        //utxo作为交易的输入
//        List<TransactionInput> inputs = transaction.getInputs();
//    }

    public static void main(String[] args) {
        List<String> keys = new ArrayList<>();
        keys.add("02d8b3a0d0f5bd6146abe8d7a1b5121e9bdbd3f2ca53aa4e4d525c67944e6561e3");
        keys.add("0236cf178423fdce9ad8b975a597d26b164a71423687b1112f64f50abcbcbf387c");
        keys.add("02e93dbc5af84c6ff4f39bc1084436c3e6d8c088fc7025827217b44cdb7b8da24a");
        //1. 生成script
        Script redeemScript = getRedeemScript(2, keys);
        //2. 地址
        Address multiSigAddress = MultiSigUtil.createMultiSigAddress(redeemScript);
        //3. 通过utxo的txHash值，获取rawTx，请求以下接口
        //https://testnet.smartbit.com.au/tx/a4ad3c64ca830f06bc7bc25432a74c271443817c40be84f8edbe9e8f23f67f7f/hex
        List<String> txHexList = new ArrayList<>();
        txHexList.add("02000000000101d2de18e2e633c8cca6a882c33f59967d10b06a3435965d37c58ad80b91f11ca90100000017160014429dcdf5dd13fdb2ffb8426874600778f155df08feffffff02102700000000000017a914882c0596a48ee7a29649f79cc221c3491c0a867187a5623a000000000017a914599d7cf45392bf20c16a7a9af3c831f21c689ff28702473044022031ab3042ebe94275353ca796985cf07d087fa438e0f505fe605433ada8efe6e302205032055b0033700ae82108dcef9456a7f12be0dc7ae2c20123786b118a66c9f501210388d61412b593e4fbb156f73a7bd68002595b26c5768d3c3e0e36401de287a24606861800");
        txHexList.add("0100000001c4e970d367022e3f8dff731f60535066cfcfe92928ddcac5f538b9335beca82301000000fdfe00004830450221009947140905190553d7f6cbb31e737902160458bebb0d8d7f40d8d0f79479e1af02207b4ed7663198c7baffa2782d8023889feb1aa46246895fd8fc196ed779f7f99101483045022100bb9b4123cb463f2fb8d96c5c56595aad004752b5e1bbdd7b312fc5bbfe4c1802022045b783cfbd498bc3cc7c7ba5df64324a443200bd6ca591f06628739bcc82d9fa014c69522102d8b3a0d0f5bd6146abe8d7a1b5121e9bdbd3f2ca53aa4e4d525c67944e6561e3210236cf178423fdce9ad8b975a597d26b164a71423687b1112f64f50abcbcbf387c2102e93dbc5af84c6ff4f39bc1084436c3e6d8c088fc7025827217b44cdb7b8da24a53aeffffffff02102700000000000017a914c4b4a8e14f7dc4319417b814d40a39a4da5fcb8187b9e962000000000017a914882c0596a48ee7a29649f79cc221c3491c0a86718700000000");
        //4. 创建交易
        Transaction rawTx = createMultiSigTx("0.0001", multiSigAddress.toString(),
                "2NBBJrLYvnDPUknvpH4AFZgVVwds9W6qyr6",
                txHexList,"6000");
        //5. 创建交易的rawTx，hex值，
        String rawHexTx = getHexTx(rawTx);
        //6. 创建交易的待签名hash值
        List<String> rawHashTxs = createMultiSigHashTx(txHexList.size(), rawTx, redeemScript);
        //7. 我对该交易的hash签名, 签名值也上传服务器
        //8. 服务器将我和其他人的签名排序后返回
        List<List<String> >lists = new ArrayList<>();
        List<String> signTxBytes1 = new ArrayList<>();
        signTxBytes1.add(signTxHash(rawHashTxs, "cTgohRSaHJnvwrS6qPHsPBMQhNKexThjxCLXP2g397Svvehzik7H").get(0));
        signTxBytes1.add(signTxHash(rawHashTxs, "cUNFWAEJ5sfoPXiBhMXgpcYMxJ7Y6bHbYsSZqHxGEMhoLSoVm6yH").get(0));
        lists.add(signTxBytes1);
        List<String> signTxBytes2 = new ArrayList<>();
        signTxBytes2.add(signTxHash(rawHashTxs, "cTgohRSaHJnvwrS6qPHsPBMQhNKexThjxCLXP2g397Svvehzik7H").get(1));
        signTxBytes2.add(signTxHash(rawHashTxs, "cUNFWAEJ5sfoPXiBhMXgpcYMxJ7Y6bHbYsSZqHxGEMhoLSoVm6yH").get(1));
        lists.add(signTxBytes2);
        //9. 对交易进行最后的打包签名，生成待发送的rawTx
        String hexTx = completeMultiSigTx(rawHexTx, redeemScript, lists);
        System.out.println("最后的rawTx:" + hexTx);
    }
}
